/**
 * Namespace responsável pela seleção de elementos através de seletores CSS. 
 * Baseado em 
 * [[http://extjs.com/deploy/ext/docs/output/Ext.DomQuery.html|DomQuery]].
 * 
 * @name jProton.query
 * @namespace
 */
/** @ignore */
jProton.query = function(){
  /**#@+ 
   * @private 
   * @function
   * @memberOf jProton.query
   * @extends core
   */

  var cache = {}, 
    simpleCache = {}, 
    reTrim = /^\s+|\s+$/g,
    reTemplate = /\{(\d+)\}/g,
    reMode = /^(\s?[\/>+~]\s?|\s|$)/,
    reTag = /^(#)?([\w-\*]+)/,
    reNth = /(\d*)n\+?(\d*)/, 
    reNth2 = /\D/,

    // This is for IE MSXML which does not support expandos.
    // IE runs the same speed using setAttribute, however FF slows way down
    // and Safari completely fails so they need to continue to use expandos.
    isIE = !!window.ActiveXObject,
    key = 30803;

  // this eval is stop the compressor from
  // renaming the variable to something shorter
  eval("var batch = 30803;");

  function _quickId(e, mode, context, val){
    if(e == context){
      var d = context.ownerDocument || context;
      return d.getElementById(val);
    }
    e = _getNodes(e, mode, "*");
    return _byId(e, val);
  };

  var _byId = function(e, val){
    if(e.tagName || e == document)
      e = [e];

    var r = new Array(), rl = -1;
    for(var i = 0, ei; ei = e[i]; i++)
      if(ei && ei.id == val){
        r[++rl] = ei;
        return r;
      }
    return r;
  };

  var _byTag = function(e, val){
    if(e.tagName || e == document)
      e = [e];

    var r = new Array(), rl = -1;
    val = val.toLowerCase();
    for(var i = 0, ei; ei = e[i]; i++)
      if(ei.nodeType == 1 && ei.tagName.toLowerCase()==val)
        r[++rl] = ei;

    return r;
  };

  var _byClass = function(e, val){
    if(!val)
      return e;

    var r = new Array(), rl = -1;
    for(var i = 0, ei; ei = e[i]; i++)
      if((' '+ei.className+' ').indexOf(val) != -1)
        r[++rl] = ei;

    return r;
  };

  var _byAttr = function(e, attr, op, val){
    var r = new Array(), rl = -1, f = query.operators[op];
    for(var i = 0, ei, a; ei = e[i]; i++){
      if(attr == "class" || attr == "className")
        a = ei.className;
      else if(attr == "for")
        a = ei.htmlFor;
      else if(attr == "href")
        a = ei.getAttribute("href", 2)
      else
        a = ei.getAttribute(attr);

      if((f && f(a, val)) || (!f && a))
        r[++rl] = ei;
    }
    return r;
  };

  var _byPseudo = function(e, name, val){
    return query.pseudos[name](e, val);
  };

  var _getNodes = function(e, mode, name){
    var r = new Array(), rl = -1;
    if(!e)
      return r;

    name = name || "*";
    if(typeof e.getElementsByTagName != "undefined")
      e = [e];

    if(!mode){
      for(var i = 0, ei, n; ei = e[i]; i++){
        n = ei.getElementsByTagName(name);
        for(var j = 0, nj; nj = n[j]; j++)
          r[++rl] = nj;
      }
    }else if(mode == "/" || mode == ">"){
      var utag = name.toUpperCase();
      for(var i = 0, ei, n; ei = e[i]; i++){
        n = ei.children || ei.childNodes;
        for(var j = 0, nj; nj = n[j]; j++)
          if(nj.nodeName == utag || nj.nodeName == name  || name == '*')
            r[++rl] = nj;

      }
    }else if(mode == "+"){
      var utag = name.toUpperCase();
      for(var i = 0, ei; ei = e[i]; i++){
        while((ei = ei.nextSibling) && ei.nodeType != 1);
        if(ei && (ei.nodeName == utag || ei.nodeName == name || name == '*'))
          r[++rl] = ei;
      }
    }else if(mode == "~"){
      for(var i = 0, ei; ei = e[i]; i++){
        while((ei = ei.nextSibling) && (ei.nodeType != 1 || 
              (name == '*' || ei.tagName.toLowerCase()!=name)));
        if(ei)
          r[++rl] = ei;
      }
    }
    return r;
  };

  var _unique = function(e){
    if(!e)
      return new Array();

    var len = e.length, i, r = e, ei;
    if(!len || typeof e.nodeType != "undefined" || len == 1)
      return e;

    if(isIE && typeof e[0].selectSingleNode != "undefined") {
      var d = ++key;
      e[0].setAttribute("__unique", d);
      var r = [e[0]], rl = 0;
      for(var i = 1, ei; ei = e[i]; i++)
        if(!ei.getAttribute("__unique") != d){
          ei.setAttribute("__unique", d);
          r[++rl] = ei;
        }
  
      for(var i = 0; ei = e[i]; i++)
        ei.removeAttribute("__unique");
  
      return r;
    }

    var d = ++key;
    e[0].__unique = d;
    for(i = 1, ei; ei = e[i]; i++){
      if(ei.__unique != d)
        ei.__unique = d;
      else{
        r = new Array(), rl = -1;
        for(var j = 0; j < i; j++)
          r[++rl] = e[j];

        for(j = i+1; ej = e[j]; j++)
          if(ej.__unique != d){
            ej.__unique = d;
            r[++rl] = ej;
          }
        return r;
      }
    }
    return r;
  };

  var _diff = function(e, e2){
    if(!e.length)
      return e2;

    if(isIE && e[0].selectSingleNode) {
      var d = ++key, ei;
      for(var i = 0; ei = e[i]; i++)
        ei.setAttribute("__diff", d);
  
      var r = new Array(), rl = -1;
      for(var i = 0; ei = e2[i]; i++)
        if(ei.getAttribute("__diff") != d)
          r[++rl] = ei;
  
      for(var i = 0; ei = e[i]; i++)
        ei.removeAttribute("__diff");
  
      return r;
    }

    var d = ++key;
    for(var i = 0; ei = e[i]; i++)
      ei.__diff = d;

    var r = new Array(), rl = -1;
    for(var i = 0; ei = e2[i]; i++)
      if(ei.__diff != d)
        r[++rl] = ei;

    return r;
  };

  var _next = function(e){
    while((e = e.nextSibling) && e.nodeType != 1);
    return e;
  };

  var _prev = function(e){
    while((e = e.previousSibling) && e.nodeType != 1);
    return e;
  };

  /**#@-*/
  /**#@+ @extends core */
  return /** @lends jProton.query */ {
    /**
     * Compiles a selector/xpath query into a reusable function. The returned function
     * takes one parameter "context" (optional), which is the context node from where the query should start.
     * @param {String} selector The selector/xpath query
     * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
     * @return {Function}
     */
    compile: function(selector, type){
      type = type || "select";

      var s = selector, ls,
        rules = query.rules,
        mm, tm, rm, matched, fl = 0, 
        f = ["var fn=function(c){\nvar mode; var e=c||document; ++batch;\n"];

      // accept leading mode switch
      mm = s.match(reMode);
      if(mm && mm[1]){
        f[++fl] = 'mode="'+mm[1].replace(reTrim, "")+'";';
        s = s.replace(mm[1], "");
      }

      // strip leading slashes
      while(s.substr(0, 1)=="/")
        s = s.substr(1);

      while(s && ls != s){
        ls = s;
        tm = s.match(reTag);
        if(type == "select"){
          if(tm){
            if(tm[1] == "#")
              f[++fl] = 'e = _quickId(e, mode, c, "'+tm[2]+'");';
            else
              f[++fl] = 'e = _getNodes(e, mode, "'+tm[2]+'");';

            s = s.replace(tm[0], "");
          }else if(s.substr(0, 1) != '@')
            f[++fl] = 'e = _getNodes(e, mode, "*");';

        }else
          if(tm){
            if(tm[1] == "#")
              f[++fl] = 'e = _byId(e, "'+tm[2]+'");';
            else
              f[++fl] = 'e = _byTag(e, "'+tm[2]+'");'

            s = s.replace(tm[0], "");
          }

        while(!(mm = s.match(reMode))){
          matched = false;
          for(var j = 0; rm = rules[j]; j++){
            var m = s.match(rm.re);
            if(m){
              f[++fl]=rm.select.replace(reTemplate, function(e,i){return m[i];});
              s = s.replace(m[0], "");
              matched = true;
              break;
            }
          }
          // prevent infinite loop on bad selector
          if(!matched)
            throw 'Error parsing selector, parsing failed at "' + s + '"';
        }
        if(mm[1]){
          f[++fl] = 'mode="'+mm[1].replace(reTrim, "")+'";';
          s = s.replace(mm[1], "");
        }
      }
      f[++fl] = "return _unique(e);\n}";
      eval(f.join(""));
      return fn;
    },

    /**
     * Selects a group of elements.
     * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
     * @param {Node} context (optional) The start of the query (defaults to document).
     * @return {Array}
     */
    select: function(selector, context){
      context = context || document;

      if(typeof context == "string")
        context = document.getElementById(context);

      var selectors = selector.split(","), len = selectors.length, results = new Array();
      for(var i = 0; i < len; i++){
        var s = selectors[i].replace(reTrim, "");
        if(!cache[s])
          if(!(cache[s] = query.compile(s)))
            throw s + " is not a valid selector";

        var result = cache[s](context);
        if(result && result != document)
          results = results.concat(result);
      }
      if(len > 1)
        return _unique(results);

      return results;
    },

    /**
     * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
     * @param {String/HTMLElement/Array} e An element id, elvalent or array of elements
     * @param {String} selector The simple selector to test
     * @return {Boolean}
     */
    is: function(e, ss){
      if(typeof e == "string")
        e = document.getElementById(e);

      var isArray = !!e.splice,
          result = query.filter(isArray ? e : [e], ss);
      return isArray ? (result.length == e.length) : (result.length > 0);
    },

    /**
     * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
     * @param {Array} e An array of elements to filter
     * @param {String} selector The simple selector to test
     * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
     * the selector instead of the ones that match
     * @return {Array}
     */
    filter: function(e, ss, nonMatches){
      ss = ss.replace(reTrim, "");
      if(!simpleCache[ss])
        simpleCache[ss] = query.compile(ss, "simple");

      var result = simpleCache[ss](e);
      return nonMatches ? _diff(result, e) : result;
    },

    /**
     * Collection of matching regular expressions and code snippets.
     */
    rules: [{
        re: /^\.([\w-]+)/,
        select: 'e = _byClass(e, " {1} ");'
      }, {
        re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
        select: 'e = _byPseudo(e, "{1}", "{2}");'
      },{
        re: /^\[([\w-]+)(?:(.?=)["']?(.+?)["']?)?\]/,
        select: 'e = _byAttr(e, "{1}", "{2}", "{3}");'
      }, {
        re: /^#([\w-]+)/,
        select: 'e = _byId(e, "{1}");'
      }
    ],

    /**
     * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
     * New operators can be added as long as the match the format <i>c</i>= where <i>c</i> is any character other than space, &gt; &lt;.
     */
    operators: {
      "=": function(attr, val){
        return attr == val;
      },
      "!=": function(attr, val){
        return attr != val;
      },
      "^=": function(attr, val){
        return attr && attr.substr(0, val.length) == val;
      },
      "$=": function(attr, val){
        return attr && attr.substr(attr.length-val.length) == val;
      },
      "*=": function(attr, val){
        return attr && attr.indexOf(val) !== -1;
      },
      "%=": function(attr, val){
        return (attr % val) == 0;
      },
      "|=": function(attr, val){
        return attr && (attr == val || attr.substr(0, val.length+1) == val+'-');
      },
      "~=": function(attr, val){
        return attr && (' '+attr+' ').indexOf(' '+val+' ') != -1;
      }
    },

    /**
     * Collection of "pseudo class" processors. Each processor is passed the current nodeset (array)
     * and the argument (if any) supplied in the selector.
     */
    pseudos: {
      "first-child": function(e){
        var r = new Array(), rl = -1, n;
        for(var i = 0, ei; ei = n = e[i]; i++){
          while((n = n.previousSibling) && n.nodeType != 1);
          if(!n)
            r[++rl] = ei;
        }
        return r;
      },

      "last-child": function(e){
        var r = new Array(), rl = -1, n;
        for(var i = 0, ei; ei = n = e[i]; i++){
          while((n = n.nextSibling) && n.nodeType != 1);
          if(!n)
            r[++rl] = ei;
        }
        return r;
      },

      "nth-child": function(e, val) {
        var r = new Array(), rl = -1, m = reNth.exec(val == "even" && "2n" || 
          val == "odd" && "2n+1" || !reNth2.test(val) && "n+" + val || val),
          f = (m[1] || 1) - 0, l = m[2] - 0;
        for(var i = 0, ei; ei = e[i]; i++){
          var p = ei.parentNode;
          if (batch != p._batch) {
            var j = 0;
            for(var n = p.firstChild; n; n = n.nextSibling)
              if(n.nodeType == 1)
                 n.nodeIndex = ++j;

            p._batch = batch;
          }
          if (f == 1) {
            if (l == 0 || ei.nodeIndex == l)
              r[++rl] = ei;

          } else if ((ei.nodeIndex + l) % f == 0)
            r[++rl] = ei;
        }

        return r;
      },

      "only-child": function(e){
        var r = new Array(), rl = -1;;
        for(var i = 0, ei; ei = e[i]; i++)
          if(!_prev(ei) && !_next(ei))
            r[++rl] = ei;

        return r;
      },

      "empty": function(e){
        var r = new Array(), rl = -1;
        for(var i = 0, ei; ei = e[i]; i++){
          var n = ei.childNodes, j = -1, nj, empty = true;
          while(nj = n[++j])
            if(nj.nodeType == 1 || nj.nodeType == 3){
              empty = false;
              break;
            }

          if(empty)
            r[++rl] = ei;
        }
        return r;
      },

      "contains": function(e, val){
        var r = new Array(), rl = -1;
        for(var i = 0, ei; ei = e[i]; i++)
          if((ei.textContent||ei.innerText||'').indexOf(val) != -1)
            r[++rl] = ei;

        return r;
      },

      "nodeValue": function(e, val){
        var r = new Array(), rl = -1;
        for(var i = 0, ei; ei = e[i]; i++)
          if(ei.firstChild && ei.firstChild.nodeValue == val)
            r[++rl] = ei;

        return r;
      },

      "checked": function(e){
        var r = new Array(), rl = -1;
        for(var i = 0, ei; ei = e[i]; i++)
          if(ei.checked == true)
            r[++rl] = ei;

        return r;
      },

      "not": function(e, val){
        return query.filter(e, val, true);
      },

      "any": function(e, val){
        var r = new Array(), rl = -1, s, ss = val.split('|');
        for(var i = 0, ei; ei = e[i]; i++)
          for(var j = 0; s = ss[j]; j++)
            if(query.is(ei, s)){
              r[++rl] = ei;
              break;
            }

        return r;
      },

      "odd": function(e){
        return this["nth-child"](e, "odd");
      },

      "even": function(e){
        return this["nth-child"](e, "even");
      },

      "nth": function(e, val){
        return e[val-1] || new Array();
      },

      "first": function(e){
        return e[0] || new Array();
      },

      "last": function(e){
        return e[e.length-1] || new Array();
      },

      "has": function(e, val){
        var r = new Array(), rl = -1, s = query.select;
        for(var i = 0, ei; ei = e[i]; i++)
          if(s(val, ei).length > 0)
            r[++rl] = ei;

        return r;
      },

      "next": function(e, val){
        var r = new Array(), rl = -1, is = query.is;
        for(var i = 0, ei; ei = e[i]; i++){
          var n = _next(ei);
          if(n && is(n, val))
            r[++rl] = ei;
        }
        return r;
      },

      "prev": function(e, val){
        var r = new Array(), rl = -1, is = query.is;
        for(var i = 0, ei; ei = e[i]; i++){
          var n = _prev(ei);
          if(n && is(n, val))
            r[++rl] = ei;
        }
        return r;
      }
    }
  }
}();

// Atalho para jProton.query
var query = jProton.query;
